//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the \"Software\"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
// forward declaration, ensures anything external to this cannot call this function
//----------------------------------------------------------------------------------------------------------------------
local calculate_template_parameter_count = null

//----------------------------------------------------------------------------------------------------------------------
// Describes a function for code generation
//----------------------------------------------------------------------------------------------------------------------
class Function extends CodeGenerator
{
  owner = null

  template_type = null
  return_type = null
  parameters = null
  specialisation = null

  body = null

  constructor(_name, _flags = 0)
  {
    name = _name
    flags = _flags
    owner = null

    template_type = null
    return_type = Parameter()
    parameters = array(0)

    body = FunctionBody()
  }

  function is_function()
  {
    return true
  }

  // overload base is_templated for handling CodeGenerator.kPointer
  //
  function is_templated()
  {
    if (has_flags(CodeGenerator.kTemplate))
    {
      return true
    }

    if (has_flags(CodeGenerator.kPointer))
    {
      local argument_count = parameters.len()
      return (calculate_template_parameter_count(this, argument_count) > 0)
    }

    return false
  }
  
  function is_specialisation()
  {
    if (has_flags(CodeGenerator.kSpecialisation))
    {
      return true
    }

    if (return_type.is_specialisation())
    {
      return true
    }

    for (local i = 0; i != parameters.len(); ++i)
    {
      if (parameters[i].is_specialisation())
      {
        return true
      }
    }

    return false
  }

  function has_header_data()
  {
    return true
  }

  function has_inline_data()
  {
    if (has_flags(CodeGenerator.kPointer))
    {
      return true
    }

    if (has_flags(CodeGenerator.kInline))
    {
      return true
    }

    if (has_flags(CodeGenerator.kTemplate))
    {
      return true
    }

    if (typeof(owner) == "instance" && owner.is_templated())
    {
      return true
    }

    local argument_count = parameters.len()
    if (calculate_template_parameter_count(this, argument_count) > 0)
    {
      return true
    }

    return false
  }

  function has_source_data()
  {
    return !has_inline_data()
  }

  // Writes a function declaration using this description.
  //
  function write_declaration(stream)
  {
    if (is_specialisation())
    {
      return
    }

    if (!has_flags(CodeGenerator.kPointer))
    {
      if (owner == null)
      {
        stream.add_separator()
      }

      // template<typename RT>
      //
      write_template(stream)

      // any keywords ie. inline
      //
      write_pre_declaration(stream)
    }

    // Parameter::write_type returns whether an extra space is required
    //
    if (return_type.write_type(stream))
    {
      stream.write(" ")
    }

    write_name_declaration(stream)

    write_parameters_declaration(stream)

    // used by subclass method for const, pure virtual, override
    write_post_declaration(stream)
  }

  // Called before the return type is written but after template parameters are written in the declaration:
  //
  // template<typename RT>
  // <called here>RT func();
  //
  function write_pre_declaration(stream)
  {
    if (!has_flags(CodeGenerator.kPointer))
    {
      if (owner == null && has_flags(CodeGenerator.kInline))
      {
        stream.write("inline ")
      }

      if (has_flags(CodeGenerator.kStatic))
      {
        stream.write("static ")
      }
      else if (owner != null && has_flags(CodeGenerator.kVirtual))
      {
        stream.write("virtual ")
      }
    }
  }

  // Writes funcname
  //
  function write_name_declaration(stream)
  {
    if (has_flags(CodeGenerator.kPointer))
    {
      stream.write("(")

      if (owner != null)
      {
        assert(typeof(owner) == "instance")
        assert(owner.name.len() > 0)
        assert(typeof(owner.name) == "string")

        stream.write("%s::", owner.name)
      }

      stream.write("*")
    }

    assert(typeof(name) == "string")
    assert(name.len() > 0)
    stream.write("%s", name)

    if (has_flags(CodeGenerator.kPointer))
    {
      stream.write(")")
    }
  }
  
  function write_parameters_declaration(stream)
  {
    stream.write("(")
    local argument_count = parameters.len()
    if (argument_count > 0)
    {
      local i = 0
      for (; i != argument_count - 1; ++i)
      {
        parameters[i].write_declaration(stream)
        stream.write(", ")
      }

      parameters[i].write_declaration(stream)
    }
    stream.write(")")
  }

  // Called after the function parameters are written in the declaration:
  //
  // void func()<called here>;
  //
  function write_post_declaration(stream)
  {
    if (owner != null)
    {
      if (has_flags(CodeGenerator.kConst))
      {
        stream.write(" const")
      }

      if (!has_flags(CodeGenerator.kPointer))
      {
        if (has_flags(CodeGenerator.kVirtual | CodeGenerator.kPureVirtual))
        {
          stream.write(" = 0")
        }
      }
    }

    if (!has_flags(CodeGenerator.kPointer))
    {
      stream.write(";")
      stream.new_line()
    }
  }

  // Writes a function definition using this description.
  //
  function write_definition(stream, file_type)
  {
    assert(typeof(file_type) == "string")
    assert(file_type == "inline" || file_type == "source")
    local is_inline = (file_type == "inline")

    if (is_inline)
    {
      if (!has_inline_data())
      {
        return false
      }
    }
    else
    {
      if (!has_source_data())
      {
        return false
      }
    }

    if (!has_flags(CodeGenerator.kPointer))
    {
      if (owner == null)
      {
        assert(typeof(name) == "string")
        assert(name.len() > 0)

        stream.add_separator()
        stream.write("// %s", name)
        if (is_templated())
        {
          if (is_specialisation())
          {
            stream.write("<%s>", specialisation)
          }
          else
          {
            stream.write("<%s>", template_type)
          }
        }
        stream.new_line()
        stream.add_separator()
      }

      // template<typename RT>
      //
      write_template(stream)

      // any keywords ie. inline
      //
      write_pre_definition(stream)
    }

    // Parameter::write_type returns whether an extra space is required
    //
    if (return_type.write_type(stream))
    {
      stream.write(" ")
    }

    write_name_definition(stream)

    write_parameters_definition(stream, file_type)

    // used by subclass method for const, pure virtual, override
    write_post_definition(stream)

    if (!has_flags(CodeGenerator.kPointer))
    {
      // write the actual contents of the function
      //
      stream.new_line()

      stream.write("{")

      body.write_definition(stream)

      stream.write("}")

      stream.new_line()
    }

    return true
  }

  // Called before the return type is written but after template parameters are written in the definition:
  //
  // template<typename RT>
  // <called here>RT func();
  //
  function write_pre_definition(stream)
  {
    if (!has_flags(CodeGenerator.kPointer))
    {
      if (has_flags(CodeGenerator.kInline))
      {
        stream.write("inline ")
      }
    }
  }

  function write_name_definition(stream)
  {
    if (has_flags(CodeGenerator.kPointer))
    {
      stream.write("(")
    }

    if (owner != null)
    {
      assert(typeof(owner) == "instance")
      assert(owner.name.len() > 0)
      assert(typeof(owner.name) == "string")

      stream.write("%s", owner.name)
      if (owner.is_templated())
      {
        if (!has_flags(CodeGenerator.kPointer))
        {
          if (is_specialisation())
          {
            stream.write("<%s>", specialisation)
          }
          else
          {
            stream.write("<%s>", owner.template_type)
          }
        }
      }
      stream.write("::")
    }

    if (has_flags(CodeGenerator.kPointer))
    {
      stream.write("*")
    }

    if (!has_flags(CodeGenerator.kUnused))
    {
      assert(typeof(name) == "string")
      assert(name.len() > 0)
      stream.write(name)
    }

    if (has_flags(CodeGenerator.kPointer))
    {
      stream.write(")")
    }
  }
  
  function write_parameters_definition(stream, file_type)
  {
    stream.write("(")
    local argument_count = parameters.len()
    if (argument_count > 0)
    {
      local i = 0
      for (; i != argument_count - 1; ++i)
      {
        parameters[i].write_definition(stream, file_type)
        stream.write(", ")
      }

      parameters[i].write_definition(stream, file_type)
    }
    stream.write(")")
  }

  // Called after the function parameters are written in the definition:
  //
  // void func()<called here>
  // {
  // }
  //
  function write_post_definition(stream)
  {
    if (owner != null && has_flags(CodeGenerator.kConst))
    {
      stream.write(" const")
    }
  }

  // Writes any template parameter declaration required by this function:
  //
  // template<typename RT, typename P1>
  // RT func(P1 arg);
  //
  function write_template(stream)
  {
    local argument_count = parameters.len()
    local template_parameter_count = calculate_template_parameter_count(this, argument_count)

    local has_written_typename = false
    if (template_parameter_count > 0)
    {
      if (!has_flags(CodeGenerator.kPointer))
      {
        stream.write("template<")
      }

      if (has_flags(CodeGenerator.kPointer))
      {
        if (owner != null)
        {
          assert(typeof(owner) == "instance")
          if (owner.is_templated())
          {
            assert(typeof(owner.name) == "string")
            stream.write("typename %s", owner.name)
            has_written_typename = true
          }
        }
      }
      else
      {
        if (has_flags(CodeGenerator.kTemplate))
        {
          if (!is_specialisation())
          {
            if (has_written_typename)
            {
              stream.write(", ")
            }

            assert(typeof(template_type) == "string")
            assert(template_type.len() > 0)
            stream.write("typename %s", template_type)
            has_written_typename = true
          }
          else
          {
            --template_parameter_count
          }
        }
      }

      if (return_type.is_templated())
      {
        if (!return_type.is_specialisation())
        {
          if (has_written_typename)
          {
            stream.write(", ")
          }

          return_type.write_template(stream)
          has_written_typename = true
        }
        else
        {
          --template_parameter_count
        }
      }

      if (argument_count > 0 && template_parameter_count > 0)
      {
        for (local i = 0; i != argument_count; ++i)
        {
          if (parameters[i].is_templated())
          {
            if (!parameters[i].is_specialisation() || parameters[i].is_function())
            {
              if (has_written_typename)
              {
                stream.write(", ")
              }

              parameters[i].write_template(stream)
              has_written_typename = true
            }
            else
            {
              --template_parameter_count
            }
          }
        }
      }

      if (!has_flags(CodeGenerator.kPointer))
      {
        stream.write(">")
        stream.new_line()
      }
    }
  }
}

//----------------------------------------------------------------------------------------------------------------------
// Checks the return type and parameters for Parameter.kTemplate and returns how many have the flag set.
//----------------------------------------------------------------------------------------------------------------------
calculate_template_parameter_count = function(_function, argument_count)
{
  local template_parameter_count = 0

  if (_function.has_flags(CodeGenerator.kPointer))
  {
    if (_function.owner != null)
    {
      assert(typeof(_function.owner) == "instance")
      if (_function.owner.is_templated())
      {
        ++template_parameter_count
      }
    }
  }
  else
  {
    // note: do not call is_template here as it is potentially recursive when CodeGenerator.kPointer is set.
    if (_function.has_flags(CodeGenerator.kTemplate))
    {
      ++template_parameter_count
    }
  }

  if (_function.return_type.is_templated())
  {
    ++template_parameter_count
  }

  for (local i = 0; i != argument_count; ++i)
  {
    if (_function.parameters[i].is_templated())
    {
      ++template_parameter_count
    }
  }

  return template_parameter_count
}